# -*- coding: utf-8 -*-
from UserDict import IterableUserDict

from BTrees.OOBTree import OOBTree
from persistent import Persistent
from persistent.dict import PersistentDict
from persistent.list import PersistentList
from zope.annotation.interfaces import IAnnotations, IAttributeAnnotatable
from zope.component import adapts
from zope.event import notify
from zope.interface import implements
from zope.lifecycleevent import ObjectAddedEvent, ObjectMovedEvent, ObjectRemovedEvent
from zope.location.interfaces import ILocation

from pargo.interfaces import IContainer, IOrdering
from pargo.events import ContainerModifiedEvent

_marker = object()

class Container(Persistent, IterableUserDict):
    """
    Container for named objects.
    """
    implements(IContainer)
    
    def __init__(self):
        """
        Initialize 
        """
        IterableUserDict.__init__(self)
        self.data = OOBTree()
        Persistent.__init__(self)        
        
    def __nonzero__(self):
        """
        A folder is something, even if it's empty.
        """
        return True

    def __setitem__(self, id, obj):
        """
        Store the named object in the folder.
        """
        ## set __name__ and __parent__ if the object supports it
        if ILocation.providedBy(obj):
            oldname = getattr(obj, '__name__', None)
            oldparent = getattr(obj, '__parent__', None)
            if id is not oldname:
                obj.__name__ = id
            if self is not oldparent:
                obj.__parent__ = self
        
        result = super(Container, self).__setitem__(id, obj)
        notify(ContainerModifiedEvent(self))
        if oldparent is None:
            notify(ObjectAddedEvent(obj))
        else:
            notify(ObjectMovedEvent(obj, oldparent, oldname, self, id))
        return result

    def __delitem__(self, id):
        """
        Remove the named object from the folder.
        """
        try:
            obj = self.get(id, _marker)
            if obj is not _marker:
                if ILocation.providedBy(obj):
                    obj.__parent__ = None
                    obj.__name__ = None
        except AttributeError:
            ## No need to fail if we can't set these
            pass
        
        result = super(Container, self).__delitem__(id)
        notify(ContainerModifiedEvent(self))
        notify(ObjectRemovedEvent(obj))
        return result


class OrderedContainer(Container):
    """
    Folder with 
    """
    implements(IAttributeAnnotatable)

    def __setitem__(self, id, obj):
        """
        Store the named object in the folder.
        """
        result = super(OrderedContainer, self).__setitem__(id, obj)
        IOrdering(self).notifyAdded(id)
        return result

    def __delitem__(self, id):
        """
        Remove the named object from the folder.
        """
        result = super(OrderedContainer, self).__delitem__(id)
        IOrdering(self).notifyRemoved(id)
        return result

    def iterkeys(self):
        """
        F.iterkeys() -> an iterator over the keys of F
        """
        return IOrdering(self).iterIdsInOrder()
    
    def keys(self):
        """
        F.keys() -> list of F's keys
        """
        return IOrdering(self).idsInOrder()
    
    def itervalues(self):
        """
        F.itervalues() -> an iterator over the values of F
        """
        for key in self.iterkeys():
            yield self[key]
    
    def values(self):
        """
        F.values() -> list of F's values
        """
        return list(self.itervalues())
    
    def iteritems(self):
        """
        F.iteritems() -> an iterator over the (key, value) items of F
        """
        for key in self.iterkeys():
            yield (key, self[key])
    
    def items(self):
        """
        F.items() -> list of F's (key, value) pairs, as 2-tuples
        """
        return list(self.iteritems())


class DefaultOrdering(object):
    """
    This implementation uses annotations to store the order on the object.
    
    Largely borrowed from `plone.folder` package.
    """
    implements(IOrdering)
    adapts(IContainer)

    ORDER_KEY = "pargo.content.container.ordering.order"
    POS_KEY = "pargo.content.container.ordering.position"

    def __init__(self, context):
        self.context = context

    def notifyAdded(self, id):
        """ see interfaces.py """
        order = self._order(True)
        pos = self._pos(True)
        order.append(id)
        pos[id] = len(order) - 1

    def notifyRemoved(self, id):
        """ see interfaces.py """
        order = self._order()
        pos = self._pos()
        idx = pos[id]
        del order[idx]
        # we now need to rebuild pos since the ids have shifted
        pos.clear()
        for n, id in enumerate(order):
            pos[id] = n

    def moveObjectsByDelta(self, ids, delta, subset_ids=None):
        """ see interfaces.py """
        order = self._order()
        pos = self._pos()
        min_position = 0
        if isinstance(ids, basestring):
            ids = [ids]
        if subset_ids is None:
            subset_ids = self.idsInOrder()
        elif not isinstance(subset_ids, list):
            subset_ids = list(subset_ids)
        if delta > 0:                   # unify moving direction
            ids = reversed(ids)
            subset_ids.reverse()
        counter = 0
        for id in ids:
            try:
                old_position = subset_ids.index(id)
            except ValueError:
                continue
            new_position = max(old_position - abs(delta), min_position)
            if new_position == min_position:
                min_position += 1
            if not old_position == new_position:
                subset_ids.remove(id)
                subset_ids.insert(new_position, id)
                counter += 1
        if counter > 0:
            if delta > 0:
                subset_ids.reverse()
            idx = 0
            for i in range(len(order)):
                if order[i] in subset_ids:
                    id = subset_ids[idx]
                    try:
                        order[i] = id
                        pos[id] = i
                        idx += 1
                    except KeyError:
                        raise ValueError('No object with id "%s" exists.' % id)

        notify(ContainerModifiedEvent(self.context))
        return counter

    def moveObjectsUp(self, ids, delta=1, subset_ids=None):
        """ see interfaces.py """
        return self.moveObjectsByDelta(ids, -delta, subset_ids)

    def moveObjectsDown(self, ids, delta=1, subset_ids=None):
        """ see interfaces.py """
        return self.moveObjectsByDelta(ids, delta, subset_ids)

    def moveObjectsToTop(self, ids, subset_ids=None):
        """ see interfaces.py """
        return self.moveObjectsByDelta(ids, -len(self._order()), subset_ids)

    def moveObjectsToBottom(self, ids, subset_ids=None):
        """ see interfaces.py """
        return self.moveObjectsByDelta(ids, len(self._order()), subset_ids)

    def moveObjectToPosition(self, id, position):
        """ see interfaces.py """
        delta = position - self.getObjectPosition(id)
        if delta:
            return self.moveObjectsByDelta(id, delta)

    def orderObjects(self, key, reverse=None):
        """ see interfaces.py """
        order = self._order()
        pos = self._pos()
        keyfn = lambda id: getattr(self.context.get(id), key)
        order.sort(None, keyfn, bool(reverse))
        for n, id in enumerate(order):
            pos[id] = n
        return -1

    def getObjectPosition(self, id):
        """ see interfaces.py """
        pos = self._pos()
        if id in pos:
            return pos[id]
        else:
            raise ValueError('No object with id "%s" exists.' % id)

    def idsInOrder(self):
        """ see interfaces.py """
        return list(self._order())
    
    def iterIdsInOrder(self):
        """ see interfaces.py """
        return iter(self._order())

    # Annotation lookup with lazy creation
    def _order(self, create=False):
        annotations = IAnnotations(self.context)
        if create:
            return annotations.setdefault(self.ORDER_KEY, PersistentList())
        else:
            return annotations.get(self.ORDER_KEY, [])

    def _pos(self, create=False):
        annotations = IAnnotations(self.context)
        if create:
            return annotations.setdefault(self.POS_KEY, PersistentDict())
        else:
            return annotations.get(self.POS_KEY, {})
